package com.xiyuan.smartutils.template.engine;

import com.xiyuan.smartutils.StrUtils;
import com.xiyuan.smartutils.Validates;
import com.xiyuan.smartutils.template.engine.exception.StatementException;
import com.xiyuan.smartutils.template.engine.statement.Statement;
import com.xiyuan.smartutils.template.engine.statement._Text;

import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;

@SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
public class StatementParser {
    private static final int MIN_LEN_OF_TAG;
    
    static {
        int min = 99999;
        TagType[] arr = TagType.values();
        for (int i = 0; i < arr.length; i++) {
            TagType type = arr[i];
            if (type == TagType.TEXT || Validates.isBlank(type.getTagBegin())) {continue;}
            
            min = Math.min(min, type.getTagBegin().length());
        }
        MIN_LEN_OF_TAG = min;
    }
    
    private String input;
    private int lineNum = 1;
    private int column = 0;
    private String path;// 资源文件
    
    private Template page;// 根节点
    private AtomicInteger pos = new AtomicInteger(-1);
    private StringBuilder value = new StringBuilder();// 实际token值得长度
    private int comment_index_start = -1;// 记录当前注释开始索引当
    private LinkedList<Statement> stack = new LinkedList<Statement>();// 当前作用域>
    
    public StatementParser(String path, String content) {
        this.input = content;
        this.page = new Template(path);
        this.path = path;
    }
    
    public boolean hasNext() {
        if (input == null) {return false;}
        
        return pos.get() < (input.length() - 1);
    }
    
    public int pos() {
        return pos.get();
    }
    
    private int nextPos() {
        if (input == null) {return -1;}
        
        if (pos.get() == input.length()) {return input.length();}
        
        return pos.incrementAndGet();
    }
    
    public char next() {
        if (!hasNext()) {return 0;}
        
        char ch = input.charAt(this.nextPos());
        if (ch == '\n') {
            this.lineNum++;
            this.column = 0;
        }
        if (ch != '\r') {this.column++;}
        
        return ch;
    }
    
    public int getLineNum() {
        return lineNum;
    }
    
    public int getColumn() {
        return this.column;
    }
    
    public String getPath() {
        return path;
    }
    
    public void setPath(String path) {
        this.path = path;
    }
    
    public Template get() {
        return page;
    }
    
    private boolean doRemoveComment() {
        if (comment_index_start == -1) {comment_index_start = getEndWithIndex(TagType.COMMENT.getTagBegin());}
        
        // 没有找到 注释
        if (comment_index_start == -1) {return false;}
        
        String comment_end = TagType.COMMENT.getTagEnd();
        int end = getEndWithIndex(comment_end);
        // 注释未结束
        if (end == -1) {return true;}
        
        value.delete(comment_index_start, end + comment_end.length());
        comment_index_start = -1;
        
        return true;
    }
    
    /**
     * 比较input最后几个字符（忽略一个换行符）是否与 str, 相同。该方法等类似 String.endWith()
     */
    private int getEndWithIndex(CharSequence target) {
        return getEndWithIndex(this.value, target);
    }
    
    public int getEndWithIndex(CharSequence src, CharSequence target) {
        if (target == null) {return -1;}
        
        if (src.length() < target.length()) {return -1;}
        
        int max = target.length();
        int offset = src.length() - target.length();
        for (int i = offset, j = 0; j < max; i++, j++) {
            char c = src.charAt(i);
            if (c != target.charAt(j)) {
                return -1;// 字符不一致
            }
        }
        
        return offset;
    }
    
    // 查找标签打开字符 <#xxx 开头
    private TagType findBeginTag() {
        
        TagType[] arr = TagType.values();
        for (int i = 0; i < arr.length; i++) {
            TagType type = arr[i];
            if (type == TagType.TEXT || Validates.isBlank(type.getTagBegin())) {continue;}
            
            if (getEndWithIndex(type.getTagBegin()) != -1) {return type;}
        }
        
        return null;
    }
    
    
    private Statement nextStatement() throws Exception {
        if (input == null) {return null;}
        
        Statement stmt = null;
        TagType stmtType = null;
        // 上次是否为解析完成
        boolean reset = true, isInit = value.length() > 0;
        while ((hasNext() || isInit)) {
            // 非上次未完成解析的需要重新读取
            if (!isInit) {value.append(this.next());}
            
            // 标记上次读取的数据处理完成
            if (isInit) {isInit = false;}
            
            // 1. 剔除注释内容
            if (doRemoveComment()) {continue;}
            
            // 2 查找标签定义开始
            if (stmtType == null) {
                // 没有找到标签
                if (value.length() >= MIN_LEN_OF_TAG && !Validates.isWhitespace(value.charAt(value.length() - 1))) {
                    stmtType = findBeginTag();
                }
                // 沒找到标签
                if (stmtType == null) {continue;}
            }
            
            // 2.1标签开始之前是否有数据 则当 _Text 标签处理
            int offset = value.indexOf(stmtType.getTagBegin());
            if (offset > 0) {
                String val = value.substring(0, offset);
                stmt = new _Text(this).setStatement(val);
                value.delete(0, val.length());
                reset = false;
                break;
            }
            
            // 3.创建新的标签
            stmt = stmtType.newStatement(this);
            if (stmt != null) {
                stmt.setStatement(value.toString());
            }
            break;// 标签被找到了
        }
        
        if (stmt == null && value.length() > 0) {
            stmt = new _Text(this).setStatement(value.toString());// 如果是最后的字符
        }
        
        try {
            int curr = this.pos();
            boolean b = stmt.parseTag(this);// 解析标签标签就绪
            if (!b)// 解析失败 当做text 处理
            {
                this.pos.set(curr);// 重新这只解析未知，因为，parseTag为移动pos
                if (stmt.getType() == TagType.ELSE && hasNext()) {
                    value.append(next());
                    return null;// 因为后续有可能是 elseif
                }
                
                stmt = new _Text(this).setStatement(value.toString());
                stmt.setCompleteTag();
            }
        }
        catch (Throwable e) {
            if (e instanceof StatementException) {throw e;}
            String path = stmt.getPath(), message = e.getMessage();
            int ln = stmt.getLineNo(), cl = stmt.getBeginColumn();
            throw new StatementException(path, ln, cl, message, e);
        }
        
        if (reset) value.setLength(0);
        
        return stmt;
    }
    
    public StatementParser parse() throws Exception {
        this.value.setLength(0);
        while (hasNext()) {
            Statement stmt = nextStatement();
            this.addStatement(stmt);
        }
        
        this.input = null;// 解析完成释放资源
        return this;
    }
    
    public void addStatement(Statement stmt) throws Exception {
        if (stmt == null) {return;}
        
        Statement scope = stack.peekLast();
        if (scope == null && stmt.getType().isBlockEnd()) {
            return;// 没有域 并且 是结束标签
        }
        
        // 检查标签范围限制
        if ((stmt.getType() == TagType.ELSEIF || stmt.getType() == TagType.ELSE)) {
            TagType t1 = stmt.getType(), t2 = TagType.IF, t3 = TagType.ELSEIF;
            String path = stmt.getPath(), msg = StrUtils.format("%s标签必须在 %s 或者 %s 标签后", t1, t2, t3);
            StatementException e = new StatementException(path, stmt.getLineNo(), stmt.getBeginColumn(), msg);
            if (scope.getType() == TagType.ELSEIF) {scope = scope.getParent();}
            else if (scope.getType() != TagType.IF) {throw e;}
        }
        
        // 检查 return
        if (stmt.getType() == TagType.RETURN) {
            // return 标签必须在 function 内
            boolean find = false;
            for (Statement scope2 : stack) {
                if (scope2.getType() == TagType.FUNCTION) {
                    find = true;
                    break;
                }
            }
            if (!find) {
                String msg = stmt.getType() + "标签必须在" + TagType.FUNCTION + "标签内";
                throw new StatementException(stmt.getPath(), stmt.getLineNo(), stmt.getBeginColumn(), msg);
            }
        }
        
        // break 或者 continue
        if (stmt.getType() == TagType.BREAK || stmt.getType() == TagType.CONTINUE) {
            // break|continue 标签必须在 for 内
            boolean find = false;
            for (Statement scope2 : stack) {
                if (scope2.getType() == TagType.FOR) {
                    find = true;
                    break;
                }
            }
            if (!find) {
                String msg = stmt.getType() + "标签必须在" + TagType.FUNCTION + "标签内";
                throw new StatementException(stmt.getPath(), stmt.getLineNo(), stmt.getBeginColumn(), msg);
            }
        }
        
        // 判断结束标签域的有效性
        if (stmt.getType().isBlockEnd()) {//
            if (stmt.getType() == TagType.END_IF) {// 那么要移除所有的 elseif else 以及if
                scope = stack.peekLast();
                while (scope != null && (scope.getType() == TagType.IF || scope.getType() == TagType.ELSEIF || scope.getType() == TagType.ELSE)) {
                    stack.removeLast();// 移除作用域
                    scope = stack.peekLast();
                }
            }
            else {
                
                // 标签结束但类型不匹配，例如 域内有 <#function> <#if>,当前结束标签为</#function> 或者 </#for>,
                if (stmt.getType().beginTag() != scope.getType()) {
                    for (Statement scope2 : stack) {
                        // 报错<#if>缺少 </#if>
                        if (stmt.getType().beginTag() == scope2.getType()) {
                            
                            String path = scope.getPath();
                            String msg = scope.getType() + "缺少结束标签" + scope.getType().getTagEnd();
                            throw new StatementException(path, scope.getLineNo(), scope.getBeginColumn(), msg);
                        }
                    }
                    // 如果结束标签的开始域不在，域内，则提示缺少开始域，例如 域内有 <#function> <#if> ，当前标签为 </#for>，则提示 缺少<#for>
                    String msg = stmt.getType() + "缺少标签" + stmt.getType().beginTag();
                    throw new StatementException(stmt.getPath(), stmt.getLineNo(), stmt.getBeginColumn(), msg);
                }
                
                stack.removeLast();// 移除作用域
            }
            return;
        }
        
        if (scope != null) {scope.addStatement(stmt);}
        else {page.addStatement(stmt);}
        
        // 快标签入栈
        if (stmt.getType().isBlockTag()) {
            stack.add(stmt);
        }
    }
    
    /**
     * 页面解析
     *
     * @param content 模板内容
     * @return 执行器
     * @throws Exception 解析异常
     */
    
    public static Template parseContent(String path, String content) {
        StatementParser parser = new StatementParser(path, content);
        try {
            return parser.parse().get();
        }
        catch (Throwable e) {
            if (e instanceof StatementException) {
                // 解析报错了直接把错误信息响应给前台
                Template view = new Template(path);
                Statement text = new _Text(parser).setStatement(e.getMessage());
                view.addStatement(text);
                view.setError(e);
                return view;
            }
            else {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
}
